/*
 * ESValue.java - ScriptME
 * 
 * Copyright (c) 2009 Cesar Henriques <cesar at alttab.com.ar>.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v2.1
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * 
 * Based on FESI Project
 * 
 * Contributors:
 * 	Jean-Marc Lugrin - initial API and implementation
 * 	Cesar Henriques <cesar at alttab.com.ar> - J2ME Porting and Extensions
 */
package org.scriptme.ecmascript.data;

import java.util.Enumeration;

import org.scriptme.ecmascript.exceptions.EcmaScriptException;
import org.scriptme.ecmascript.interpreter.Evaluator;

/**
 * All EcmaScript values are subclasses of this class. The ESValue support many
 * operations which may not be implemented by all type (and then generate an
 * error), to simplify type checking.
 * <P>
 * ESReference are currently not value - see ESReference.
 */
public abstract class ESValue {

	// Codes for the getTypeOf, used to implement "=="
	/** The Constant EStypeUndefined. */
	public static final int EStypeUndefined = 1;

	/** The Constant EStypeNull. */
	public static final int EStypeNull = 2;

	/** The Constant EStypeBoolean. */
	public static final int EStypeBoolean = 3;

	/** The Constant EStypeNumber. */
	public static final int EStypeNumber = 4;

	/** The Constant EStypeString. */
	public static final int EStypeString = 5;

	/** The Constant EStypeObject. */
	public static final int EStypeObject = 6;

	// The following routines access the value as a primitive type. They are
	// the prefered way to access the value of a primitive type or the default
	// value of an object if its type is known.
	// If the object is a reference, it will be dereferenced until a value is
	// found, this may generate an error or return a dummy value.
	//
	// toString() is considered part of these data access routine, however
	// it may never fail. It is implemented as described in 9.8.

	/**
	 * Returns a detailed description of the value, intended for debugging.
	 * (toString returns the official string representation, as defined in 9.8).
	 * 
	 * @return the detailled information
	 */
	abstract public String toDetailString();

	/**
	 * Return a Java object which is the object to pass to Java routines called
	 * by FESI. This may be the corresponding Java object (for example the
	 * String), or a wrapper object. When received back from a Java routine, an
	 * equivallent (but probably not identical) object must be built.
	 * 
	 * @return a Java object equivallent to the EcmaScript object.
	 */
	public abstract Object toJavaObject();

	/**
	 * Return the double value of this ESValue as defined in 9.3, throw an
	 * exception if not defined.
	 * 
	 * @return a double
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                Thrown because by default this is not supported
	 */
	public double doubleValue() throws EcmaScriptException {
		throw new EcmaScriptException("Conversion to double unsupported by "
				+ this);
	}

	/**
	 * Return the boolean value of this ESValue as defined in 9.2, throw an
	 * exception if not defined.
	 * 
	 * @return a boolean
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                Thrown because by default this is not supported
	 */
	public boolean booleanValue() throws EcmaScriptException {
		throw new EcmaScriptException("Conversion to boolean unsupported by "
				+ this);
	}

	/**
	 * Return the EcmaScript object of this ESValue as definined in 9.9, throw
	 * an exception if not defined.
	 * 
	 * @param evaluator
	 *            the evaluator
	 * 
	 * @return an ESObject
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                Thrown because by default this is not supported
	 */
	public ESValue toESObject(Evaluator evaluator) throws EcmaScriptException {
		throw new EcmaScriptException("Conversion to object unsupported by "
				+ this);
	}

	// The following routines are derived from the doubleValue of the
	// ESvalue. They may be overriden for efficiency by classes which
	// contain an integer or other equivallent.

	/**
	 * Return the Integer value, as defined in 9.4.
	 * 
	 * @return An integer inside a double
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                Not thrown
	 */
	public double toInteger() throws EcmaScriptException {
		double value = this.doubleValue();
		if (Double.isNaN(value)) {
			return 0.0;
		} else if ((value == 0.0) || Double.isInfinite(value)) {
			return value;
		} else {
			return (double) ((long) value);
		}
	}

	/**
	 * Return the 32 bit integer, as defined in 9.5
	 * 
	 * @return The signed 32 bit integer
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                Not thrown
	 */
	public int toInt32() throws EcmaScriptException {
		double value = this.toInteger();
		return (int) value;
	}

	/**
	 * Returned the unsigned 32 bit integer (9.6). Currently implemented as
	 * toInt32 !
	 * 
	 * @return The integer
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                Not thrown
	 */
	public int toUInt32() throws EcmaScriptException {
		double value = this.toInteger();
		return (int) value;
	}

	/**
	 * Return the unsigned 16 bit integer (9.7). Currently ignore the sign
	 * issue.
	 * 
	 * @return The unsigned as a short
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                Not thrown
	 */
	public short toUInt16() throws EcmaScriptException {
		double value = this.toInteger();
		return (short) value;
	}

	// Convertion to EcmaScript primitive type - rebuild the primitive type
	// based
	// on the convertion to the Java primitive type. May be overriden by a
	// subclass
	// for efficiency purpose (especially if it does not require a conversion).

	// In fact on toESNumber is used (to implement the operator +), and this
	// is a very minor performance enhancement - so the routine could be
	// easily supressed

	/**
	 * Convert the value to an EcmaScript boolean (9.2) if possible
	 * 
	 * @return The EcmaScript boolean value
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                Not thrown
	 */
	public ESValue toESBoolean() throws EcmaScriptException {
		return ESBoolean.makeBoolean(this.booleanValue());
	}

	/**
	 * Convert the value to an EcmaScript string (9.8) if possible
	 * 
	 * @return The EcmaScript string value (there is always one!)
	 */
	public ESValue toESString() {
		return new ESString(this.toString());
	}

	/**
	 * Convert the value to an EcmaScript number (9.3) if possible
	 * 
	 * @return The EcmaScript number value
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                From doubleValue
	 */
	public ESValue toESNumber() throws EcmaScriptException {
		double d = this.doubleValue();
		return new ESNumber(d);
	}

	// Provide support to easily distinguish primitive values from other, and
	// to convert values to primitive value. If the desired type is known
	// the direct conversion routines are prefered.

	/**
	 * Return true if the value is a built-in primitive.
	 * 
	 * @return true if a primitive
	 */
	abstract public boolean isPrimitive();

	/**
	 * Transform to a primitive as described in 9.1
	 * 
	 * @return A primitive value
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                If conversion is impossible
	 */
	abstract public ESValue toESPrimitive() throws EcmaScriptException;

	/**
	 * Transform to a primitive as described in 9.1, with a specified hint
	 * 
	 * @param preferedType
	 *            the prefered type
	 * 
	 * @return a primitive value
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                If conversion is impossible
	 */
	abstract public ESValue toESPrimitive(int preferedType)
			throws EcmaScriptException;

	// [[Call]] support (to ease check of type)
	/**
	 * Call function.
	 * 
	 * @param thisObject
	 *            the this object
	 * @param arguments
	 *            the arguments
	 * 
	 * @return the eS value
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 */
	public ESValue callFunction(ESObject thisObject, ESValue[] arguments)
			throws EcmaScriptException {
		throw new EcmaScriptException("Function called on non object: " + this);
	}

	// [[Construct]] support (to ease check of type)
	/**
	 * Do construct.
	 * 
	 * @param thisObject
	 *            the this object
	 * @param arguments
	 *            the arguments
	 * 
	 * @return the eS object
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 */
	public ESObject doConstruct(ESObject thisObject, ESValue[] arguments)
			throws EcmaScriptException {
		throw new EcmaScriptException("'new' called on non object: " + this);
	}

	// abstract public ESValue doNewObject();

	/**
	 * Information routine to check if a value is a number (for array indexing)
	 * if true, must implement conversions to double and int without evaluator.
	 * 
	 * @return true if a number.
	 */
	public boolean isNumberValue() {
		return false;
	}

	/**
	 * Information routine to check if a value is a string if true, must
	 * implement toString without a evaluator.
	 * 
	 * @return true if a String (ESString or StringPrototype).
	 */
	public boolean isStringValue() {
		return false;
	}

	/**
	 * Information routine to check if a value is a boolean if true, must
	 * implement booleanValue without a evaluator.
	 * 
	 * @return true if a boolean (ESBoolean or BooleanPrototype).
	 */
	public boolean isBooleanValue() {
		return false;
	}

	/**
	 * Return a code indicating the type of the object for the implementation of
	 * the "==" operator.
	 * 
	 * @return A type code
	 */
	public abstract int getTypeOf();

	/**
	 * Return the name of the type of the object for the typeof operator.
	 * 
	 * @return The name of the type as a String
	 */
	public abstract String getTypeofString();

	// Support to list description of objects

	/**
	 * Return true if the value is composite (even if not an object). A
	 * composite value can be examined by getAllDescriptions. A composite value
	 * may have no component!
	 * 
	 * @return true if composite
	 */
	abstract public boolean isComposite();

	/**
	 * Return an enumeration of all description of elements of this value (for
	 * example properties of an object).
	 * 
	 * @return Enumerator of all components or NULL.
	 */
	public Enumeration getAllDescriptions() {
		return null;
	}

	/**
	 * Returns a full description of the value, with the specified name.
	 * 
	 * @param name
	 *            The name of the value to describe
	 * 
	 * @return the description of this value
	 */
	abstract public ValueDescription getDescription(String name);

	/**
	 * Returns a full description of the unnamed value.
	 * 
	 * @return the description of this value
	 */
	public ValueDescription getDescription() {
		return getDescription(null);
	}
}
